path 记录了遍历路径,并且还实现了一系列增删改的 api,会在遍历 ast 的时候传递给 visitor 的回调函数。
这节我们来实现下 path。
# 思路分析
path 是节点之间的关联,每一个 path 记录了当前节点和父节点,并且 path 和 path 之间也有关联。

通过 path 我们可以找到父节点、父节点的父节点,一直到根节点。
path 的实现就是在 traverse 的时候创建一个对象来保存当前节点和父节点,并且能够拿到节点也就能对节点进行操作,可以基于节点来提供一系列增删改的 api。
# 代码实现
首先我们创建一个 path 的类,记录当前节点 node,父节点 parent 以及父节点的 path。
class NodePath {
constructor(node, parent, parentPath) {
this.node = node;
this.parent = parent;
this.parentPath = parentPath;
}
}
@前端进阶之旅: 代码已经复制到剪贴板
然后在遍历的时候创建 path 对象,传入 visitor。
function traverse(node, visitors, parent, parentPath) {
const defination = astDefinationsMap.get(node.type);
let visitorFuncs = visitors[node.type] || {};
if(typeof visitorFuncs === 'function') {
visitorFuncs = {
enter: visitorFuncs
}
}
const path = new NodePath(node, parent, parentPath);
visitorFuncs.enter && visitorFuncs.enter(path);
if (defination.visitor) {
defination.visitor.forEach(key => {
const prop = node[key];
if (Array.isArray(prop)) { // 如果该属性是数组
prop.forEach(childNode => {
traverse(childNode, visitors, node, path);
})
} else {
traverse(prop, visitors, node, path);
}
})
}
visitorFuncs.exit && visitorFuncs.exit(path);
}
@前端进阶之旅: 代码已经复制到剪贴板
之后 visitor 里面就可以拿到 path 了。
比如我们可以在 visotor 里从当前节点一直查找到根节点:
traverse(ast, {
Identifier: {
exit(path) {
pa